﻿namespace StyleCop.CSharp.JslOrderingRules
{
    using System;
    using System.Linq;

    /// <summary>
    /// Verifies that members are in alphabetical order.
    /// </summary>
    public class MembersMustBeInAlphabeticalOrder : CsRuleBase<RuleAnalyzer>
    {
        #region Constructors
        /// <summary>
        /// Initializes a new instance of the <see cref="MembersMustBeInAlphabeticalOrder"/> class.
        /// </summary>
        /// <param name="analyzer">The analyzer.</param>
        public MembersMustBeInAlphabeticalOrder(RuleAnalyzer analyzer)
            : base(analyzer)
        {
        }
        #endregion Constructors

        #region Public Methods
        /// <summary>
        /// Analyzes the document.
        /// </summary>
        public override void AnalyzeDocument()
        {
            this.Document.WalkDocument(this.AnalyzeElement);
        }

        /// <summary>
        /// Analyzes the element.
        /// </summary>
        /// <param name="element">The element to analyze.</param>
        /// <param name="parentElement">The parent element.</param>
        /// <param name="context">The context.</param>
        /// <returns>Always <c>true</c>.</returns>
        public bool AnalyzeElement(CsElement element, CsElement parentElement, object context)
        {
            if (MembersMustBeInAlphabeticalOrder.ShouldProcessElement(element))
            {
                this.VerifyElementIsInCorrectOrder(element, parentElement);
            }

            return true;
        }
        #endregion Public Methods

        #region Private Methods
        private static bool ShouldProcessElement(CsElement element)
        {
            if (element.Generated)
            {
                return false;
            }

            // Process any element that should be ordered relative to its siblings.
            ElementType type = element.ElementType;
            return type == ElementType.Class
                || type == ElementType.Constructor
                || type == ElementType.ConstructorInitializer
                || type == ElementType.Delegate
                || type == ElementType.Destructor
                || type == ElementType.Enum
                || type == ElementType.Event
                || type == ElementType.ExternAliasDirective
                || type == ElementType.Field
                || type == ElementType.Indexer
                || type == ElementType.Interface
                || type == ElementType.Method
                || type == ElementType.Namespace
                || type == ElementType.Property
                || type == ElementType.Struct;
        }

        private void VerifyElementIsInCorrectOrder(CsElement element, CsElement parentElement)
        {
            var siblingsThatShouldBeBeforeThisElement =
                from siblingElement in parentElement.ChildElements
                where element.ElementType == siblingElement.ElementType
                    && element.IsStatic() == siblingElement.IsStatic()
                    && element.IsReadOnly() == siblingElement.IsReadOnly()
                    && element.ActualAccess == siblingElement.ActualAccess
                    && string.Compare(element.Name, siblingElement.Name, StringComparison.OrdinalIgnoreCase) > 0
                    && element.Location.StartPoint.Index < siblingElement.Location.StartPoint.Index
                orderby siblingElement.Name descending
                select siblingElement;

            CsElement closestSibling = siblingsThatShouldBeBeforeThisElement.FirstOrDefault();
            if (closestSibling != null)
            {
                this.AddViolation(element, RuleAnalyzer.MembersMustBeInAlphabeticalOrder, element.Name, closestSibling.Name);
            }
        }
        #endregion Private Methods
    }
}